<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>Bestdori谱面检查工具</title>
  <style type="text/css">
      * {
          margin: 0;
          padding: 0;
      }

      html, body {
          margin: 0;
          padding: 0;
          font: 12px/1.5 tahoma, arial, 'hiragino sans gb', 'microsoft yahei', sans-serif;
          -webkit-font-smoothing: antialiased;
      }

      .button {
          height: 2em;
          border-width: 0px;
          border-radius: 3px;
          background: #1E90FF;
          cursor: pointer;
          outline: none;
          color: white;
      }

      .button:hover {
          background: #5599FF;
      }

      #bestdori-code {
          border: 1px solid #eaeaea;
          outline: none;
          width: 600px;
          resize: none;
          background: rgb(250, 250, 250);
          line-height: 24px;
          font-size: 14px;
          float: left;
          padding: 10px 8px;
          color: black;
          font-family: inherit;
          box-sizing: border-box;
      }
  </style>
</head>
<body>
<div style="width: 100%; max-width: 1200px; margin: 0 auto;">
  <div style="margin: 12px">
    <div style="text-align: center">
      <div style="font-size: 2em; font-weight: bold;">Bestdori谱面检查工具</div>
      <div style="font-size: 1em;">作者：灵喵（<a href="https://github.com/ReiKohaku">Github</a> | <a href="https://gitee.com/ReiKohaku">Gitee</a> | <a href="https://space.bilibili.com/88633132">Bilibili</a>）</div>
      <div style="font-size: 0.75em;">懒得做美化了，差不多就行了</div>
      <div style="font-size: 0.75em;">你说你要不会改就别改，改了还老出错，出错了就别嫌用着麻烦XD</div>
      <div style="font-size: 0.75em;">制作比较匆忙，可能有没考虑到的错误，如果出现问题还请务必及时联系我修改~</div>
	  <div style="font-size: 0.75em;">
	  <span>更新日志：</span><br />
	  <span>2022-02-14：检查时会自动去除滑条中的beat和lane属性。</span>
	  </div>
    </div>
    <div id="checker-frame" style="margin: 16px 0 0 0;">
      <div>请在下方的输入框内填入您要检查的<b>Bestdori自制谱面代码</b></div>
      <textarea id="bestdori-code" rows="10" autofocus="true" style="margin: 12px 0 0 0; width: 100%;"></textarea>
      <input type="button" class="button" value="检查" style="margin: 12px 0 0 0; width: 100%;" onclick="javascript:check()"/>
    </div>
    <div id="check-result" style="margin: 16px 0 0 0;">
    </div>
  </div>
</div>
<script>
    function check() {
        const codeInput = document.getElementById("bestdori-code");
        if (!codeInput) {
            console.error("Cannot get code input DOM!");
            setOutput("error", "获取Bestdori!谱面代码失败，请联系作者解决。");
            return;
        }
        const bestdoriCode = codeInput.value;
        const chart = (function () {
            try {
                return JSON.parse(bestdoriCode);
            } catch (e) {
                console.error(e);
                return null;
            }
        })()
        if (!chart) {
            setOutput("error", "解析谱面代码失败，请确认您的谱面代码正确！");
            return;
        }
        const isOldType = false;
		let hasFixedWrongAttr = false;
        const errors = [];

        function addError(num, note, content) {
            let output = "第 " + (parseInt(num) + 1) + " 个节点";
            if (note.type === "Slide") {
                if (Array.isArray(note.connections) && note.connections.length >= 2) {
                    const min = Math.min(...note.connections.map(c => c.beat));
                    const max = Math.max(...note.connections.map(c => c.beat));
                    output += "（beat 范围为 " + min + "-" + max + "）"
                }
            } else if (typeof note.beat === "number") output += "（beat 为 " + note.beat + "）";
            output += content;
            errors.push(output);
        }

        for (const i in chart) {
            const note = chart[i];
            if (!note.type) {
                addError(i, note, "没有定义类型。");
                continue;
            }
            if (["BPM", "Single", "Slide", "Directional"].indexOf(note.type) < 0) {
                addError(i, note, "类型有误（不属于BPM、Single、Slide、Directional中的任何一个）。");
                continue;
            }
            if (note.type !== "Slide" && typeof note.beat !== "number") {
                addError(i, note, "的 beat 属性应该是一个数值。");
            }
            if (["Single", "Directional"].indexOf(note.type) >= 0) {
                if (typeof note.lane !== "number") addError(i, note, "的 beat 属性应该是一个数值。");
                else if (note.lane < 0 || note.lane > 6 || note.lane % 1 !== 0) addError(i, note, "的 lane 属性应该是一个 0 至 6 的整数。")
            }
            if (note.type === "BPM") {
                if (typeof note.bpm !== "number") {
                    addError(i, note, "的 bpm 属性应该是一个数值。");
                } else if (note.bpm <= 0) {
                    addError(i, note, "的 bpm 属性是非正数，而它应该是一个正数。");
                }
            } else if (note.type === "Single") {
                if (typeof note.flick !== "boolean" && note.flick !== undefined && note.flick !== null) {
                    addError(i, note, "的 flick 属性应为一个逻辑值（true 或 false）或留空。")
                }
            } else if (note.type === "Slide") {
                /* 2022/02/14更新：自动删除Slide物件中的错误属性 */
                if (typeof chart[i].beat !== "undefined" || typeof chart[i].lane !== "undefined") {
                    hasFixedWrongAttr = true;
                }
                delete chart[i].beat;
                delete chart[i].lane;

                if (!Array.isArray(note.connections))
                    addError(i, note, "定义为一个滑条，但没有任何 Tick。");
                else if (note.connections.length < 2)
                    addError(i, note, "定义为一个滑条，但只有 " + note.connections.length + " 个 Tick，不足以构成滑条。");
                else {
                    for (const j in note.connections) {
                        const num = parseInt(j) + 1
                        const c = note.connections[j];
                        if (typeof c.beat !== "number") {
                            addError(i, note, "的第 " + num + " 个 Tick 的 beat 属性应该是一个数值。");
                        }
                        if (typeof c.lane !== "number") {
                            addError(i, note, "的第 " + num + " 个 Tick 的 lane 属性应该是一个 0 至 6 的数值。");
                        } else if (!c.hidden && c.lane % 1 !== 0) {
                            addError(i, note, "的第 " + num + " 个 Tick 不是隐藏节点，lane 属性应该是一个 0 至 6 的整数。");
                        }
                    }
                }
            } else if (note.type === "Directional") {
                if (["Left", "Right"].indexOf(note.direction) < 0)
                    addError(i, note, "的 direction 属性有误（应为 Left 或 Right）。")
                if (typeof note.width !== "number" || note.width < 1 || note.width > 3 || note.width % 1 !== 0)
                    addError(i, note, "为侧滑键，其 width 属性应该是一个 1 至 3 的整数。");
                else {
                    if (note.direction === "Left" && note.lane - note.width + 1 < 0) {
                        addError(i, note, "是左向侧滑键，在 " + (note.lane + 1) + " 轨道上，但宽度为 " + note.width + "，超出了左侧边界。")
                    }
                    if (note.direction === "Right" && note.lane + note.width - 1 > 6) {
                        addError(i, note, "是右向侧滑键，在 " + (note.lane + 1) + " 轨道上，但宽度为 " + note.width + "，超出了右侧边界。")
                    }
                }
            }
        }
        // reformat
        let reformatCode = "[";
        reformatCode += chart.map(i => JSON.stringify(i)).join(",\r");
        reformatCode += "]";
        codeInput.value = reformatCode;
        // output
        if (!errors.length) {
            setOutput("success", "您检查的谱面代码没有问题！如果仍无法上传，请联系作者讨论。" + (hasFixedWrongAttr ? "<b>同时，检查器已为您修复谱面中可能存在的问题，如果原谱面无法正常上传，请尝试复制上方框中的谱面代码。</b>" : ""));
        } else {
            setOutput("error", "您检查的谱面代码存在如下问题：<br />" + errors.join("<br />") + "<br />检查器已自动为您重新换行，请您复制上方代码到一个有行号标注的文本编辑器内，行号即为对应节点的编号，方便进行修改。" + (hasFixedWrongAttr ? "<b>同时，检查器已为您修复谱面中可能存在的问题，建议复制上方框中的谱面代码进行修改。</b>" : ""));
        }
    }

    function setOutput(type, content) {
        const resultDisplay = document.getElementById("check-result");
        if (!resultDisplay) {
            console.error("Cannot get result display DOM!");
            return;
        }
        if (!content) content = "";
        switch (type) {
            case "error":
                resultDisplay.style["color"] = "red";
                break;
            case "success":
                resultDisplay.style["color"] = "green";
                break;
            default:
                resultDisplay.style["color"] = null;
        }
        resultDisplay.innerHTML = content;
    }
</script>
</body>
</html>
